import cgi
import re

from google.appengine.api import urlfetch
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

preamble = """<html>
  <head>
    <title>Affliction DoT Uptime Analysis</title>
    <style>
    body { font-family: Helvetica,sans }
    table {
      float:left;
      margin: 0 2em 0 0;
    }
    table tr th { text-align: center; background: #ccf; padding: .5ex 1em;}
    table tr td { text-align: center; padding: .5ex 1em; }
    .good { background: #cfc }
    .ok   { background: #ffc }
    .bad  { background: #fcc }
    h2 {
      margin: 2ex 0;
      clear:both;
    }
    </style>
  </head>
  <body>
  <h1>Affliction DoT Analysis</h1>
  <p>This tool analyses a single WWS parse for an Affliction warlock. It does so on the basis of the length of the fight, not the health of the boss, so its figures are estimates at best. Use it to compare your own results from week to week, or to find out if you're doing it wrong. The best parses to use are on static fights like Patchwerk, but it can also be useful to see how you're doing on more mobile fights. Sartharion is a good example where you should be able to get close to Patchwerk uptimes with the right tricks (hint: Demonic Circle). The analysis is independent of damage done, and thus independent of your gear level, but doesn't look at skill issues like trinket use at all.</p>
  <p><strong>Note:</strong><em> This tool is in development. It only works on links to WWS parses for affliction locks. When WWS is busy, it will fail. Try again!</em></p>
  <form action="/analyse" method="post">
    <div><input type="submit" value="Analyse WWS Link:"><input name="wwsurl" type="text" value="%s" style="width:40em"/> </div>
  </form>"""

instructions = """
  <hr/>
  <h3>How To Use This Tool</h3>
  <ol>
    <li>Go to a recent WWS Parse</li>
    <li>Select a single boss fight. Patchwerk is the canonical example.</li>
    <li>Click the name of the Affliction Warlock in the chart.</li>
    <li>Copy the URL of the warlock's summary from your address bar.</li>
    <li>Paste the URL into the box below, and click the button.</li>
  </ol>"""

def compileString(spellname):
  tmp = r'(?P<name>%s)[^,]*,(?P<link>[^,]*),(?P<damage>[^,]*?),(?P<dpc>[^,]*?),"*(?P<hits>[^,"]*)"*,(?P<hitpc>[^,]*),"*(?P<hitavg>[^,"]*)"*,"*(?P<hitmax>[^,"]*)"*,"*(?P<dots>[^,"]*?)"*,"*(?P<dotavg>[^,"]*)"*,"*(?P<dotmax>[^,"]*)"*,"*(?P<crits>[^,"]*)"*,(?P<critpc>[^,"]*),"*(?P<critavg>[^,"]*)"*,"*(?P<critmax>[^,"]*)"*'
  return tmp % spellname

def formatHaunt(match, maxtime):
  hits = int(match.group('hits') or 0)
  crits = int(match.group('crits') or 0)
  min_up = (hits + crits)*8*100/maxtime
  max_up = (hits + crits)*12*100/maxtime
  up_avg = (min_up + max_up)/2
  score  = "good" if up_avg > 95.0 else "ok" if up_avg > 75.0 else "bad"
  tmp = """<h2>Haunt Uptime</h2>
  <table>
  <tr><th>Hits</th><th>Crits</th><th>Uptime</th></tr>
  <tr class="%s"><td>%u</td><td>%u</td><td>%.1f%%&mdash;%.1f%%</td></tr>
  </table>
  <p><em>Haunt</em> uptime should be as close to 100%% as possible. While its direct damage is poor, it applies a 20%% DPS boost to all your DoTs, refreshes Corruption, and provides one application of Shadow's Embrace. The health return offsets Life Tap and raid damage, and makes life easier on raid healers. Keep <em>Haunt</em> up. Period.</p>
  <p><em>Haunt</em> has an 8s cooldown and a 12s duration, so your actual uptime was somewhere in the range shown. The upper bound should be at least 100%%</p>
  """ % (score, hits, crits, min_up, max_up)
  return tmp

def formatDot(match, optimum):
  dots = int(match.group('dots'))
  uptime = 100*dots/optimum
  if match.group('name') == "Immolate":
    score  = "good" if uptime > 70.0 else "ok" if uptime > 60.0 else "bad"
  else:
    score  = "good" if uptime > 85.0 else "ok" if uptime > 75.0 else "bad"
  tmp = """<tr class="%s"><td>%s</td><td>%u</td><td>%u</td><td>%.1f%%</td></tr>""" % (score, match.group('name'), dots, optimum, uptime)
  return tmp

def formatDrainSoul(match, optimum):
  ticks = int(match.group('dots'))
  optimum = optimum / 4 # (DS should be used for last 25% health, this is an estimate)
  return """<h2>Drain Soul</h2>
  <table>
    <tr><th>Ticks</th><th>Max. Ticks</th><th>Uptime</th></tr>
    <tr><td>%u</td><td>%u</td><td>%.1f%%</td></tr>
  </table>
  <p><em>Drain Soul</em> is no longer useless. When the boss is below 25%% it can tick for upwards of 10K damage every 3 seconds, far exceeding the damage of your normal filler, Shadow Bolt. However, you only get those numbers when you keep up your affliction effects (everything except <em>Immolate</em>). So for the last 25%% of a boss, you should be juggling DoT refreshes with Drain Soul ticks. You'll never get to the Max. Ticks shown &mdash; because you'll almost always be breaking DS to refresh DoTs &mdash; just try to maximize the number of ticks.</p>
  """ % (ticks, optimum, 100*ticks/optimum)

def formatSB(match):
  hits = int(match.group('hits'))
  crits = int(match.group('crits'))
  return """<h2>Shadow Bolt</h2>
  <table>
    <tr><th>Hits</th><th>Crits</th><th>Total</th></tr><tr>
    <tr><td>%u</td><td>%u</td><td>%u</td></tr><tr>
  </table>
  <p><em>Shadow Bolt</em> is your filler for the first 75%% of the boss's health. You should never stop casting, because as a Warlock, you should never run out of mana. It's tricky to calculate the maximum theoretical number of <em>Shadow Bolt</em>s you should be able to cast in an optimal rotation &mdash; and it will vary based on your mana return with <em>Replenishment</em>, the absolute length of the fight, and the size of your base mana pool. This is best used for comparing like with like: either your own parses from week to week, or the parses of similarly geared, similarly specced warlocks.</p>
  """ % (hits, crits, hits+crits)

class Analyse(webapp.RequestHandler):

  def post(self):
    parseUrl = self.request.get('wwsurl') or "http://wowwebstats.com/bzjakr32prbpa?a=x23425ec&s=58399-95290"
    wwsParse = urlfetch.fetch(parseUrl,"None","GET",{},False,True)

    shadowbolt = re.compile(compileString("Shadow Bolt"), re.MULTILINE)
    haunt = re.compile(compileString("Haunt"), re.MULTILINE)
    immolate = re.compile(compileString("Immolate"), re.MULTILINE)
    corruption = re.compile(compileString("Corruption"), re.MULTILINE)
    ua = re.compile(compileString("Unstable Affliction"), re.MULTILINE)
    coa = re.compile(compileString("Curse of Agony"), re.MULTILINE)
    ds = re.compile(compileString("Drain Soul"), re.MULTILINE)
    sl = re.compile(compileString("Siphon Life"), re.MULTILINE)

    time = re.compile(r'From.* .(.*)...">.* .(\d+) . of raid', re.MULTILINE)

    time_parse  = time.search(wwsParse.content)
    sb_parse    = shadowbolt.search(wwsParse.content)
    sl_parse    = sl.search(wwsParse.content)
    ua_parse    = ua.search(wwsParse.content)
    haunt_parse = haunt.search(wwsParse.content)
    coa_parse   = coa.search(wwsParse.content)
    corr_parse  = corruption.search(wwsParse.content)
    immo_parse  = immolate.search(wwsParse.content)
    ds_parse    = ds.search(wwsParse.content)

    # Corr, UA, SL and Imm are 3s dots
    # CoA is a 2s tick
    uptime  = int(time_parse.group(1))
    maxtime = uptime * 100 / int(time_parse.group(2))
    max_3s_dots = maxtime / 3
    max_2s_dots = maxtime / 2

    self.response.out.write(preamble % parseUrl)
    self.response.out.write("<p>Parse: <a href=\"%s\">%s</a></p>" % (parseUrl, parseUrl))
    self.response.out.write(formatHaunt(haunt_parse, maxtime))
    self.response.out.write(formatDrainSoul(ds_parse, max_3s_dots))
    self.response.out.write("""
        <h2>DoT Uptime</h2>
        <table>
          <tr><th>Spell</th><th>Ticks</th><th>Max. Ticks</th><th>Uptime</th></tr>""")
    self.response.out.write(formatDot(corr_parse, max_3s_dots))
    self.response.out.write(formatDot(ua_parse, max_3s_dots))
    self.response.out.write(formatDot(coa_parse, max_2s_dots))
    self.response.out.write(formatDot(immo_parse, max_3s_dots))
    self.response.out.write(formatDot(sl_parse, max_3s_dots))
    self.response.out.write("""</table>
    <p>Your DoTs are the meat of your damage and a strong indicator of your performance. If you can't keep your DoTs up, maybe you're not really an Affliction warlock. Note that the analysis doesn't take any glyphs, set bonuses or other ability modifiers into account. Recall that after 25%, Drain Soul's damage increases with the number of affliction effects on a target, so keeping them up is key to high DPS.</p>
    <ul>
    <li><em>Corruption</em> is your highest-damage DoT. Since it's refreshed by Haunt, and you should be keeping Haunt up, there's no reason you shouldn't have almost 100% uptime.</li>
    <li><em>Unstable Affliction</em> is your next highest damage-dealer. It has the same duration as <em>Immolate</em>, so casting the two adjacent to each other is always a good idea.</li>
    <li><em>Curse of Agony</em> ticks every 2s, unlike most other effects.</li>
    <li><em>Immolate</em> should not be refreshed when the boss is at or below 25% health. While it is a high-damage spell, and should be in your rotation, it's not an Affliction effect, and damage lost by not refreshing Immolate is more than made up for by a single extra tick of Drain Soul. Optimal uptime is around 75%.</li>
    <li><em>Siphon Life</em> isn't a high damage dealer, but its healing is nice, it's instant cast, and it's another Affliction effect that counts for Drain Soul.</li>
    </ul>
    """)
    self.response.out.write(formatSB(sb_parse))

    self.response.out.write("""</body>\n</html>""")


class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write(preamble % "")
    self.response.out.write(instructions)
    self.response.out.write("""</body>\n</html>""")

application = webapp.WSGIApplication(
                                     [('/', MainPage),
                                      ('/analyse', Analyse)],
                                     debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()
